package com.chatplus.application.service.pay.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.chatplus.application.client.pay.WeChatPayClient;
import com.chatplus.application.client.pay.domain.request.wechat.WeChatRefundRequest;
import com.chatplus.application.common.lock.TLock;
import com.chatplus.application.common.lock.TurnRightLock;
import com.chatplus.application.common.logging.SouthernQuietLogger;
import com.chatplus.application.common.logging.SouthernQuietLoggerFactory;
import com.chatplus.application.common.util.IdGenerator;
import com.chatplus.application.constant.PayConstants;
import com.chatplus.application.dao.pay.PayRequestDao;
import com.chatplus.application.dao.pay.RefundRequestDao;
import com.chatplus.application.domain.entity.pay.PayRequestEntity;
import com.chatplus.application.domain.entity.pay.RefundRequestEntity;
import com.chatplus.application.enumeration.RefundStatusEnum;
import com.chatplus.application.event.pay.RefundFailEvent;
import com.chatplus.application.event.pay.RefundSuccessEvent;
import com.chatplus.application.event.pay.dto.RefundRequestEvent;
import com.chatplus.application.web.satoken.helper.LoginHelper;
import com.github.binarywang.wxpay.bean.request.WxPayRefundQueryV3Request;
import com.github.binarywang.wxpay.bean.result.WxPayRefundQueryV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
import com.github.binarywang.wxpay.exception.WxPayException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.time.Instant;


@Service
public class RefundProcessor {

    private static final SouthernQuietLogger LOGGER = SouthernQuietLoggerFactory.getLogger(RefundProcessor.class);
    private final TurnRightLock turnRightLock;
    private final WeChatPayClient weChatPayClient;
    private final PayRequestDao payRequestDao;
    private final RefundRequestDao refundRequestDao;
    private final ApplicationContext applicationContext;

    public RefundProcessor(TurnRightLock turnRightLock, WeChatPayClient weChatPayClient, PayRequestDao payRequestDao, RefundRequestDao refundRequestDao, ApplicationContext applicationContext) {
        this.turnRightLock = turnRightLock;
        this.weChatPayClient = weChatPayClient;
        this.payRequestDao = payRequestDao;
        this.refundRequestDao = refundRequestDao;
        this.applicationContext = applicationContext;
    }

    public boolean applyRefund(RefundRequestEntity refundRequestEntity, PayRequestEntity payRequestEntity) {

        refundRequestEntity.setPayRequestId(payRequestEntity.getId());
        refundRequestEntity.setTradeTransactionId(payRequestEntity.getTradeTransactionId());
        refundRequestEntity.setRefundStatus(RefundStatusEnum.HANDLING);
        refundRequestEntity.setMinUserId(LoginHelper.getUserId());
        refundRequestEntity.setRefundApplyNo(IdGenerator.generateId());
        refundRequestEntity.setPlatformChannelAccount(payRequestEntity.getPlatformChannelAccount());
        refundRequestEntity.setApplyAt(Instant.now());
        // 组装请求微信退款请求体
        WeChatRefundRequest refundRequest = new WeChatRefundRequest();
        refundRequest.setMchId(payRequestEntity.getPlatformChannelAccount());
        refundRequest.setTotalMoney(payRequestEntity.getTotalMoney());
        refundRequest.setRefundMoney(refundRequestEntity.getRefundMoney());
        if (StringUtils.isNotEmpty(refundRequestEntity.getRefundDesc())) {
            refundRequest.setRefundRemark(refundRequestEntity.getRefundDesc());
        }
        refundRequest.setRefundApplySeqNo(refundRequestEntity.getRefundApplyNo());
        refundRequest.setTradeTransactionId(payRequestEntity.getTradeTransactionId());
        refundRequest.setPayTransactionId(payRequestEntity.getPayTransactionId()+"");
        //refundRequest.setAppid(payRequestEntity.getAppId());
        try {
            WxPayRefundV3Result result = weChatPayClient.applyRefund(refundRequest);
            LOGGER.message("发起微信退款").context("request", refundRequest).context("response", result).info();
            String status = result.getStatus();
            refundRequestEntity.setRefundResponseNo(result.getRefundId());
            refundRequestEntity.setErrCode(status);
            refundRequestEntity.setErrCodeDes(status);
            if (PayConstants.Wechat.TRADE_SUCCESS.equals(status) || PayConstants.Wechat.REFUND_PROCESSING.equals(status)) {
                refundRequestEntity.setRefundStatus(RefundStatusEnum.HANDLING);
                // 成功返回
                return true;
            } else {
                refundRequestEntity.setRefundStatus(RefundStatusEnum.FAIL);
            }
        } catch (Exception wxPayException) {
            LOGGER.message("发起微信退款报错").context("request", refundRequest).exception(wxPayException).error();
            // 如果退款单在微信端不存在
            //refundRequestEntity.setErrCode(wxPayException.getErrCode());
            //refundRequestEntity.setErrCodeDes(wxPayException.getErrCodeDes());
            refundRequestEntity.setRefundStatus(RefundStatusEnum.FAIL);
        } finally {
            refundRequestDao.insert(refundRequestEntity);
        }
        return false;
    }
    @Transactional(rollbackFor = Exception.class)
    public void queryRefund(RefundRequestEntity refundRequestEntity) {
        if (refundRequestEntity == null) {
            LOGGER.message("查询退款结果异常，refundRequestEntity为null")
                .warn();
            return;
        }
        WxPayRefundQueryV3Request request = new WxPayRefundQueryV3Request();
        request.setOutRefundNo(refundRequestEntity.getRefundApplyNo());
        //加锁
        String key = "Key:Lock:QueryRefund:" + refundRequestEntity.getId();
        RefundProcessor refundProcessor = (RefundProcessor) AopContext.currentProxy();
        try (TLock tLock = turnRightLock.tryLock(key)) {
            if (tLock == null) {
                return;
            }
            // 获取最新的退款状态
            refundRequestEntity = refundRequestDao.selectById(refundRequestEntity.getId());
            RefundStatusEnum noStatus = refundRequestEntity.getRefundStatus();
            // 如果为退款成功或者退款失败
            if (noStatus != RefundStatusEnum.HANDLING) {
                LOGGER.message("退款请求已处理,无需重复查询").context("request", refundRequestEntity).info();
                return;
            }
            WxPayRefundQueryV3Result result = weChatPayClient.queryRefund(refundRequestEntity);
            LOGGER.message("发起退款查询").context("request", refundRequestEntity).context("response", result).info();
            if (result != null) {
                String status = result.getStatus();
                refundRequestEntity.setErrCode(status);
                refundRequestEntity.setErrCodeDes(status);
                switch (status) {
                    case PayConstants.Wechat.TRADE_SUCCESS:
                        DateTime successTime = DateUtil.parse(result.getSuccessTime());
                        Instant successAt = successTime == null ? Instant.now(): successTime.toInstant();
                        Long refundAmount = result.getAmount().getRefund().longValue();
                        refundRequestEntity.setRefundMoney(refundAmount);
                        refundRequestEntity.setRefundResponseNo(result.getRefundId());
                        refundRequestEntity.setSuccessAt(successAt);
                        refundRequestEntity.setRefundStatus(RefundStatusEnum.SUCCESS);
                        break;
                    case PayConstants.Wechat.TRADE_CLOSED:
                        refundRequestEntity.setRefundStatus(RefundStatusEnum.CLOSED);
                        break;
                    case PayConstants.Wechat.REFUND_ABNORMAL:
                        refundRequestEntity.setRefundStatus(RefundStatusEnum.FAIL);
                        break;
                    default:
                        break;
                }
            }
            if (refundRequestEntity.getRefundStatus() != RefundStatusEnum.HANDLING) {
                refundProcessor.updateRefundForHandleRefundResult(refundRequestEntity);
            }

        } catch (WxPayException wxPayException) {
            LOGGER.message("发起退款查询报错").context("request", refundRequestEntity).exception(wxPayException).error();
            // 如果退款单在微信端不存在
            if (wxPayException.getErrCode().equals(PayConstants.Wechat.RESOURCE_NOT_EXISTS)) {
                refundRequestEntity.setErrCode(wxPayException.getErrCode());
                refundRequestEntity.setErrCodeDes(wxPayException.getErrCodeDes());
                refundRequestEntity.setRefundStatus(RefundStatusEnum.FAIL);
                refundProcessor.updateRefundForHandleRefundResult(refundRequestEntity);
            }
        } catch (Exception e) {
            LOGGER.message("发起退款查询报错").context("request", refundRequestEntity).exception(e).error();
        }
    }

    /**
     * 查询到退款结果后，更新退款状态
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateRefundForHandleRefundResult(RefundRequestEntity refundRequestEntity) {
        if (refundRequestEntity == null) {
            return;
        }
        refundRequestDao.updateById(refundRequestEntity);
        RefundStatusEnum refundStatusEnum = refundRequestEntity.getRefundStatus();

        //退款成功了，更新已退款金额，要累加
        if (refundStatusEnum == RefundStatusEnum.SUCCESS) {
            LambdaUpdateWrapper<PayRequestEntity> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.setSql(" refunded_money = ifnull(refunded_money,0) + " + refundRequestEntity.getRefundMoney());
            updateWrapper.setSql(" version = ifnull(version,0) + 1");
            updateWrapper.eq(PayRequestEntity::getId, refundRequestEntity.getPayRequestId());
            payRequestDao.update(null, updateWrapper);
        }
        RefundProcessor refundProcessor = (RefundProcessor) AopContext.currentProxy();
        refundProcessor.publishEvent(refundRequestEntity.getId());
    }

    @Transactional(rollbackFor = Exception.class)
    public void publishEvent(Long refundRequestId) {
        if (refundRequestId == null) {
            return;
        }
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    RefundRequestEntity refundRequestEntity = refundRequestDao.selectById(refundRequestId);
                    if (refundRequestEntity == null || refundRequestEntity.getPayRequestId() == null) {
                        return;
                    }
                    PayRequestEntity payRequestEntity = payRequestDao.selectById(refundRequestEntity.getPayRequestId());
                    RefundRequestEvent refundRequestEvent = new RefundRequestEvent();
                    refundRequestEvent.setOrderId(payRequestEntity.getBizId());
                    switch (refundRequestEntity.getRefundStatus()) {
                        case SUCCESS:
                            applicationContext.publishEvent(new RefundSuccessEvent(payRequestEntity.getBizCode().name(), refundRequestEvent));
                            break;
                        case FAIL:
                            applicationContext.publishEvent(new RefundFailEvent(payRequestEntity.getBizCode().name(), refundRequestEvent));
                            break;
                        default:
                            break;
                    }
                }
            });
        }
    }
}


